feat(agent): agent-native MCP wave — bridges, core surface, instance registry, devframe connect - #145
feat(agent): agent-native MCP wave — bridges, core surface, instance registry, devframe connect#145antfubot wants to merge 7 commits into
Conversation
|
Follow-up commit
Gates rerun: 905 unit tests, typecheck, lint, and the three connector e2e specs all green. (tsnapi flagged the removed bridge methods as a narrowing — allowed deliberately since they only ever existed in this unmerged stack.) |
|
Follow-up commit
Gates rerun: 905 unit tests (incl. new |
b4b9bb0 to
ef2598e
Compare
✅ Deploy Preview for devfra ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Rebased onto Conflicts resolved during the rebase (
Full gate rerun clean post-rebase: 911 unit tests, typecheck, lint, build, and 17/17 Playwright e2e. |
|
Follow-up commit cac only auto-prints help for an explicit 4 new unit tests cover bare invocation, |
…ommands bridge, git agent tools - createMcpFetchHandler: framework-agnostic web-standard MCP endpoint extracted from mountMcpHttp (now a thin h3 wrapper); exported from devframe/adapters/mcp for custom hosts (Next App Router, etc.) - built-in read_state(key?) MCP tool over shared state, honoring the exposeSharedState filter alongside the resource projection - hub commands gain opt-in agent exposure: agent field (description, safety, valibot args) projects handler-bearing commands into ctx.agent; DF8404 rejects agent exposure on group-only commands - valibot→JSON-Schema conversion moved to devframe/utils/valibot-json-schema (public) so SDK-free hosts can convert schemas - rpc: schema-typed handlers may be async — Thenable<InferReturnType<RS>> in the schema-typed definition branch - git plugin: status/log/show/branches/diff agent-flagged with valibot args/returns schemas (read-only surface; writes stay private) - docs: hub commands-as-tools, read_state, custom-host mounting; DF8404 page
…connect, in-process Next MCP - instance registry: registerDevframeInstance/readDevframeInstances/ probeDevframeInstance/listLiveDevframeInstances in devframe/node — atomic same-dir writes, prune-on-read, ghost dedup per (port, basePath), dialable-origin adoption for family-ambiguous localhost binds; createDevServer registers automatically and unregisters on close; DEVFRAME_INSTANCES_DIR / DEVFRAME_DISABLE_INSTANCE_REGISTRY overrides - first devframe bin: `devframe connect` runs the stdio MCP connector — devframe_index (discover instances + their tools, funnel hints for MCP-less servers) and devframe_call (proxy one tool call over Streamable-HTTP); errors carry actionable fix payloads; missing SDK peer throws coded DF0043 - @devframes/next: DevframeNextHost.mountMcp serves MCP in-process on the Next app's own origin (the /_next/mcp shape); hub example wires it, advertises it in connection meta, registers the instance, and agent-flags its ping command; catch-all route exports POST/DELETE - mcp adapter: drop non-object outputSchema projections (MCP requires type object; a v.void() returns schema broke SDK clients) - e2e: devframe-connect (files-inspector round-trip incl. gateway tool) and minimal-next-devframe-hub (in-process discovery + command call); hermetic per-suite registries; vitest keeps unit runs out of the global registry - diagnostics DF0042/DF0043 + docs pages; connect/registry docs in the MCP adapter page
- ctx.agent.registerToolProvider(() => AgentToolInput[]): a lazy tool source queried at list/getTool/invoke time — the same on-demand projection applied to agent-flagged RPCs; earlier sources win on id collision; handle.notifyChanged() drives tools/list_changed - hub commands host derives its agent projection through one provider: the commands map is the single source of truth, replacing the registerAgentTools/unregisterAgentTools mirror and its handle map - built-in and connector tool names follow the devframe:<area>:<fn> convention: read_state -> devframe:state:read, devframe_index -> devframe:connect:list-instances, devframe_call -> devframe:connect:call-tool
- drop the devframe/utils/valibot-json-schema subpath: AgentToolInput gains valibot args (the same shape RPC definitions carry); the agent host derives the JSON-Schema input internally, and the hub passes schemas through untouched — conversion is an implementation detail again - devframe/node exports only registerDevframeInstance (+ its two types) from the instance registry; the read/probe/prune helpers stay internal to the connector
…space main renamed examples/minimal-next-devframe-hub -> examples/next-devframe-hub and its RPC/command/instance ids to the example:next-devframe-hub convention (#143); this wave's additions (MCP serverName, registry instance id, e2e spec file + assertions, playwright cwd) now match. Same fix for files-inspector's example:files-inspector id/namespace. Also regenerates the recipes/common-rpc-functions + recipes/open-helpers dts snapshots for phase 2's Thenable<InferReturnType<RS>> handler-return widening (non-breaking — allowed to update without --allow-breaking).
Bare `devframe` (no subcommand) silently exited 0 — cac only shows help automatically for an explicit -h/--help flag, not an unmatched command. Check matchedCommand after parse() and fall back to outputHelp(), guarding against the already-handled --help case so it doesn't print twice.
main landed two breaking changes this branch didn't know about: - deps!: migrate MCP adapter to @modelcontextprotocol/sdk v2 (#156) — the monolithic package split into @modelcontextprotocol/server + @modelcontextprotocol/client; setRequestHandler moved from imported schema constants to method-string form. - feat(rpc)!: support Standard Schema for RPC definitions (#157) — RpcArgsSchema/RpcReturnSchema key off StandardSchemaV1 instead of valibot's GenericSchema; @valibot/to-json-schema dropped; single-arg JSON-Schema unwrapping removed (always arg0/arg1 now). Adaptations: - connect.ts: import from @modelcontextprotocol/server(+/stdio) and @modelcontextprotocol/client (dynamic, still peer-optional); handler registration uses 'tools/list'/'tools/call' method strings. - devframe's package.json: @modelcontextprotocol/client added as an optional peer (connect.ts uses it to dial discovered instances) and bundled correctly via tsdown's onlyBundle (client pulls in @modelcontextprotocol/core, pkce-challenge, eventsource[-parser], jose — all now declared). - Renumbered DF0042 (registry write failure) and DF0043 (missing MCP SDK) to DF0045/DF0046 — both collided with codes main allocated to unrelated diagnostics (capabilities.build:false; RPC arg/return validation) while this branch was in flight. - AgentTool/AgentToolInput.args and DevframeCommandAgentOptions.args retyped from valibot's GenericSchema[] to StandardSchemaV1[]. host-agent.ts no longer eagerly converts args to inputSchema (that module is gone); a kind: 'tool' entry now carries args raw, mirroring how an RPC-backed tool defers to ctx.rpc.definitions — the MCP adapter's computeInputSchema converts either on demand. - hub's commands→agent bridge: coercePositionalArgs no longer detects a single-object schema to unwrap (that convention is gone project- wide); always maps arg0/arg1/... positionally, matching RPC-backed tool coercion. - Tests updated for the new args-carried-raw contract, plus a new end-to-end MCP-adapter test proving Standard Schema args convert to JSON Schema over the real wire (arg0-keyed, not unwrapped). - Removed the now-fully-redundant devframe/utils/valibot-json-schema module and its registrations (superseded by the upstream to-json-schema.ts, which already covers every validator via ~standard.jsonSchema, degrading to a permissive object schema for validators without one — e.g. valibot). Verified: 1020 unit tests, typecheck (21/21), lint, full build, and 17/17 Playwright e2e (incl. both connector round-trips through the real stdio/HTTP MCP v2 pipeline) all green.
158eb99 to
2549d78
Compare
|
Rebased onto
Both required real adaptation, not just textual conflict resolution — see
Verified: 1020 unit tests, typecheck (21/21), lint, full build, and all 17 Playwright e2e specs green — including both connector round-trips running the real stdio↔HTTP MCP v2 pipeline end-to-end, not just mocked. |
Why
The full agent-native MCP wave (plan 031). Vercel's
next-devtools-mcp0.4.0 validated an architecture devframe was already positioned for: the real MCP endpoint lives inside the framework, and a thin external connector just discovers and proxies it. This wave builds both halves — the embedded surface and the connector — proven end-to-end, including the literal/_next/mcpshape on devframe primitives via@devframes/next.Supersedes #142 (phase 1) and #144 (phase 2), both folded in here.
Phase 1 — bridges & structured errors
viteDevBridgeand@devframes/next'screateDevframeNextHandlergain anmcpoption (forwarded tocreateDevServer) and advertise the side-car endpoint in their__connection.json—ConnectionMeta['mcp']gains an optionalportfor side-car origins. Shared resolverresolveMcpConnectionMeta.formatMcpErroremits{ error: { code, message, fix?, docs? } }for nosticsDiagnostics, so agents get the actionable next step and docs URL instead of a flattened string.Phase 2 — core surface
createMcpFetchHandler(ctx, options)— the MCP Streamable-HTTP endpoint as a framework-agnostic web-standardRequest → Responsehandler;mountMcpHttpis a thin h3 wrapper over it.devframe:state:readtool — tool-shaped shared-state access (no key → key list, key → JSON value), honoring theexposeSharedStatefilter alongside the resource projection.ctx.agent.registerToolProvider(() => AgentToolInput[]): a tool source queried atlist/getTool/invoketime, the same on-demand projection applied toagent-flagged RPCs. The commands host registers one provider deriving tools from its commands map — single source of truth, nothing mirrored. Commands opt in via anagentfield (description,safety, optional valibotargs);agenton a handler-less group throws DF8404; the field never crosses the wire.Thenable<InferReturnType<RS>>.status/log/show/branches/diffagent-flagged with args/returns schemas (safety: 'read'); writes stay agent-invisible. Cross-refs plan 029.Phase 3 — instance registry, connector, in-process Next MCP
devframe/node):registerDevframeInstancewrites an atomic record to~/.devframe/instances/<pid>-<port>.json; readers prune dead records on failed__connection.jsonprobes, dedup same-port ghosts (newest wins), and adopt a dialable origin for family-ambiguouslocalhostbinds.createDevServerregisters automatically and unregisters on close; in-process hosts call it explicitly.DEVFRAME_INSTANCES_DIR/DEVFRAME_DISABLE_INSTANCE_REGISTRYoverride. Failures degrade to coded warnings (DF0042).devframebin +connect— the package's first bin.devframe connectruns a stdio MCP connector exposing two gateway tools:devframe:connect:list-instances(discover live instances + list their MCP tools; MCP-less instances carry a restart-with---mcpfunnel hint) anddevframe:connect:call-tool(proxy one tool call over Streamable-HTTP). Errors carry{ message, fix }; a missing SDK peer throws DF0043.--portprobes beside the registry.DevframeNextHost.mountMcp(ctx, path)serves the fetch handler on the Next app's own origin (the/_next/mcpshape). The hub example mounts/__hub/__mcp, advertises it, registers the instance, and agent-flags its ping command.outputSchemaprojections are dropped (av.void()returns schema producedtype: null, which SDK clients reject).API surface
All wave tool names follow the
devframe:<area>:<fn>convention. The public surface is deliberately minimal — only members with a consumer outside their own package are exported (createMcpFetchHandler,registerToolProvider,registerDevframeInstance+ its two types,resolveMcpConnectionMeta, and the feature options). valibot→JSON-Schema conversion and the registry read/probe/prune helpers stay internal.Verification
Full gate:
pnpm lint && pnpm test && pnpm typecheck && pnpm build— 905 unit tests and 17/17 Playwright e2e, including two connector gates: files-inspector (discovery → gateway-tool round-trip → actionable errors) and minimal-next-devframe-hub (connector discovers the hub inside the Next dev server and calls its agent-flagged command through the in-process endpoint).This PR was created with the help of an agent.